
package cn.tang.tframe.storage.ks3;

import cn.tang.tframe.common.utils.id.UidUtil;
import cn.tang.tframe.common.utils.io.PropertiesUtil;
import cn.tang.tframe.common.utils.lang.CollectionUtils;
import cn.tang.tframe.common.utils.lang.StringUtils;
import cn.tang.tframe.context.CloudContextFactory;
import cn.tang.tframe.dynconfig.DynConfigProcessorBuilder;
import cn.tang.tframe.dynconfig.IDynConfigProcessor;
import cn.tang.tframe.dynconfig.parser.BooleanConfigValueParser;
import cn.tang.tframe.dynconfig.parser.ConfigValueParser;
import cn.tang.tframe.dynconfig.parser.StringConfigValueParser;
import cn.tang.tframe.storage.utils.FileDealUtil;
import com.ksyun.ks3.dto.Bucket;
import com.ksyun.ks3.dto.CannedAccessControlList;
import com.ksyun.ks3.dto.GetObjectResult;
import com.ksyun.ks3.dto.Ks3Object;
import com.ksyun.ks3.dto.ObjectMetadata;
import com.ksyun.ks3.dto.ResponseHeaderOverrides;
import com.ksyun.ks3.http.HttpClientConfig;
import com.ksyun.ks3.service.Ks3;
import com.ksyun.ks3.service.Ks3Client;
import com.ksyun.ks3.service.Ks3ClientConfig;
import com.ksyun.ks3.service.request.GetObjectRequest;
import com.ksyun.ks3.service.request.PutObjectRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.InputStream;
import java.util.HashSet;
import java.util.List;
import java.util.Properties;
import java.util.Set;

/**
 * Created by tanghc on 17/2/16.
 */
public class KsUploader {

	Logger logger = LoggerFactory.getLogger(this.getClass());
	private final static String defaultAk = "keccnTvtCpVYBW5d4nT1";
	private final static String defaultSk =
		"bHCLMnhzUW/FyuPIC4nLQvztum3YLS98/Ba1kNEr";

	private KsUploader() {
	}

	private static class KsUploaderHolder {


		private static final String redis_config_group = "common";
		private static final String redis_config_dataId = "storageKs3Config";
		private static final String productCode =
			MainConfigLoader.getInstance().getProductCode();
		private static final String appCode =
			MainConfigLoader.getInstance().getAppCode();
		private static Ks3ClientConfig config;
		private static Ks3 client;
		private static ConfigValueParser<String> configValueParser =
			new StringConfigValueParser();;
		private static ConfigValueParser<Boolean> booleanConfigValueParser =
			new BooleanConfigValueParser();

		static {
			DynConfigProcessorBuilder.getInstance().buildOnce(
				productCode, appCode, redis_config_group, redis_config_dataId,
				new IDynConfigProcessor() {

					@Override
					public void procesConfig(String config) {

						Properties properties =
							PropertiesUtil.loadFromString(config);
						// 初始化配置
						buildKs3Client(properties);
					}
				});
			// 初始化命名空间
//			initBuck();
		}
		private static KsUploader instance = new KsUploader();

		private static void buildKs3Client(Properties properties) {

			config = new Ks3ClientConfig();
			/**
			 * 设置服务地址</br> 中国（北京）| ks3-cn-beijing.ksyun.com 中国（上海）|
			 * ks3-cn-shanghai.ksyun.com 中国（香港）| ks3-cn-hk-1.ksyun.com
			 */
			config.setEndpoint(
				configValueParser.parse(
					properties, "endpoint", "ks3-cn-beijing.ksyun.com")); // 此处以北京region为例
			// config.setEndpoint("ks3-cn-beijing-internal.ksyun.com");
//			String protocol =
//				configValueParser.parse(properties, "protocol", "http");
			String protocol = "http";//此处全部使用http,https会有异常,需要把证书加到jre中
			protocol = protocol.toLowerCase();
			config.setProtocol(Ks3ClientConfig.PROTOCOL.valueOf(protocol));

			/**
			 * true表示以 endpoint/{bucket}/{key}的方式访问</br> false表示以
			 * {bucket}.endpoint/{key}的方式访问
			 */

			Boolean styleAccess = booleanConfigValueParser.parse(
				properties, "styleAccess", false);
			config.setPathStyleAccess(styleAccess);

			HttpClientConfig hconfig = new HttpClientConfig();
			// 在HttpClientConfig中可以设置httpclient的相关属性，比如代理，超时，重试等。
			config.setHttpClientConfig(hconfig);

			client = new Ks3Client(
				configValueParser.parse(properties, "ak", defaultAk),
				configValueParser.parse(properties, "sk", defaultSk));
			client.setKs3config(config);
		}

		private static void initBuck() {

			boolean b = false;
			Set<String> bucks = queryBucketNames();
			if(CollectionUtils.isNotEmpty(bucks)){
				for(String buck: bucks){
					if(StringUtils.equalsIgnoreCase(buck,productCode)){
						b = true;
						break;
					}
				}
			}
			if (!b) {
				client.createBucket(productCode);
			}
		}

		private static String createUrl(String key) {

			if (StringUtils.isBlank(key)) {
				return null;
			}
			return createUrlPre() + key;
		}

		private static String getKeyFromUrl(String url) {

			if (StringUtils.isBlank(url)) {
				return null;
			}
			String flag1 = productCode + "." + config.getEndpoint() + "/";
			int i = url.indexOf(flag1);
			if (i != -1) {
				i += flag1.length();
			}
			if (i == -1) {
				String flag2 = config.getEndpoint() + "/" + productCode + "/";
				i = url.indexOf(flag2);
				if (i != -1) {
					i += flag2.length();
				}
			}
			if (i == -1) {
				throw new RuntimeException("no key for url:" + url);
			}
			return url.substring(i);
		}

		private static String createUrlFormat() {

			StringBuffer sbuf = new StringBuffer();
			sbuf.append(createUrlPre());
			sbuf.append("%s");
			return sbuf.toString();
		}

		private static String createUrlPre() {

			StringBuffer sbuf = new StringBuffer();
			if (config.getProtocol() != null &&
				config.getProtocol().equals(Ks3ClientConfig.PROTOCOL.https)) {
				sbuf.append("https://");
			}
			else {
				sbuf.append("http://");
			}
			if (config.isPathStyleAccess()) {
				sbuf.append(config.getEndpoint());
				sbuf.append("/");
				sbuf.append(productCode);
				sbuf.append("/");
			}
			else {
				sbuf.append(productCode);
				sbuf.append(".");
				sbuf.append(config.getEndpoint());
				sbuf.append("/");
			}
			return sbuf.toString();
		}
		private static Set<String> queryBucketNames() {

			final Set<String> names = new HashSet<>();
			List<Bucket> buckets = client.listBuckets();
			CollectionUtils.visitIterator(
					buckets, new CollectionUtils.BeanVisitor<Bucket>() {

						@Override
						public void visit(Bucket bean) {

							names.add(bean.getName());
						}
					});
			return names;
		}
	}

	public static KsUploader getInstance() {

		return KsUploaderHolder.instance;
	}

	/**
	 * 上传文件,上传到金山文件服务器
	 * 方法结束后stream会关闭
	 * @param file
	 * @return 返回的是生成的文件的唯一标识,
	 * @注意 返回的字符串不是文件的url,如果需要绝对地址url调用getUrl(key)返回
	 */
	public String uploadFile(File file) {

		String key = null;
		try {
			key = createKey(file.getName());
			PutObjectRequest request =
				new PutObjectRequest(KsUploaderHolder.productCode, key, file);
			request.setCannedAcl(CannedAccessControlList.PublicRead);
			logger.debug("start upload file : {}", key);
			KsUploaderHolder.client.putObject(request);
			logger.debug("end upload file : {}", key);
			return key;
		}
		catch (Exception e) {
			logger.error(
				"upload file fail: key={},error={}", key, e.getMessage());
			throw new RuntimeException("upload file exception", e);
		}
	}

	/**
	 * 将一个输入流中的内容上传至<bucket名称>这个存储空间下，并命名为<object key>
	 * 上传文件,上传到金山文件服务器
	 * 方法结束后stream会关闭
	 * @return 返回的是生成的文件的唯一标识,
	 * @注意 返回的字符串不是文件的url,如果需要绝对地址url调用getUrl(key)返回
	 */
	public String uploadStream(String filename, InputStream inputStream,Long fileSize) {

		String key = null;
		try {
			key = createKey(filename);
			ObjectMetadata meta = new ObjectMetadata();
			PutObjectRequest request = new PutObjectRequest(
				KsUploaderHolder.productCode, key, inputStream, meta);
			// 可以指定内容的长度，否则程序会把整个输入流缓存起来，可能导致jvm内存溢出
			if(null != fileSize){
				meta.setContentLength(fileSize);
			}
			//上传一个公开文件
			request.setCannedAcl(CannedAccessControlList.PublicRead);
			logger.debug("start upload stream : {}", key);
			KsUploaderHolder.client.putObject(request);
			logger.debug("end upload stream : {}", key);
			return key;
		}
		catch (Exception e) {
			logger.error(
				"upload stream fail: key={},error={}", key, e.getMessage());
			throw new RuntimeException("upload stream exception", e);
		}

	}

	/**
	 * 根据文件唯一标示 返回文件url地址
	 * @param key
	 * @return
	 */
	public String getUrl(String key){
		return KsUploaderHolder.createUrl(key);
	}

	/**
	 * 返回文件url的前部分,
	 * 使用该返回值+文件的唯一标示key,可以得到文件的绝对地址url
	 * @return
	 */
	public String getUrlPre(){
		return KsUploaderHolder.createUrlPre();
	}

	private String createKey(String filename) {

		if (StringUtils.isBlank(filename)) {
			filename = "";
		}
		return StringUtils.join(StringUtils.join(new Object[]{
				KsUploaderHolder.appCode, FileDealUtil.nowHour(), UidUtil.getId()

		}, "/"), filename.substring(filename.lastIndexOf(".")));
	}

	public boolean buckExist(String buck) {

		return KsUploaderHolder.client.bucketExists(buck);
	}

	public Set<String> queryBucketNames() {
		return KsUploaderHolder.queryBucketNames();
	}

	/**
	 * <p>使用最简单的方式创建一个bucket</p> <p>将使用默认的配置，权限为私有，存储地点为杭州</p>
	 */
	public void createBucketSimple(String buck) {

		KsUploaderHolder.client.createBucket(buck);
	}

	public InputStream getInputStreamByKey(String key) {

		GetObjectRequest request =
			new GetObjectRequest(KsUploaderHolder.productCode, key);

		// 重写返回的header
		ResponseHeaderOverrides overrides = new ResponseHeaderOverrides();
		// overrides.setContentType("text/html");
		// .......
		request.setOverrides(overrides);
		// 只接受数据的0-10字节。通过控制该项可以实现分块下载
		// request.setRange(0,10);
		GetObjectResult result = KsUploaderHolder.client.getObject(request);

		Ks3Object object = result.getObject();
		// 获取object的元数据
		// ObjectMetadata meta = object.getObjectMetadata();
		// 当分块下载时获取文件的实际大小，而非当前小块的大小
		// Long length = meta.getInstanceLength();
		// 获取object的输入流
		return object.getObjectContent();
	}

	public InputStream getInputStreamByUrl(String url) {

		return getInputStreamByKey(KsUploaderHolder.getKeyFromUrl(url));
	}
	public boolean removeByKey(String key) {
		try {
			KsUploaderHolder.client.deleteObject(KsUploaderHolder.productCode,key);
			return true;
		} catch (Exception e) {
			logger.error("delete file fail: key={},error={}",key,e.getMessage());
		}
		return false;
	}
	public boolean removeByUrl(String url) {
		return removeByKey(KsUploaderHolder.getKeyFromUrl(url));
	}

}
